#include <float.h>
#include <stdexcept>
#include "Camera.h"
#include "Config.h"
#include "Definitions.h"

#if defined(ASPECT_RATIO_PRINT) || defined(RENDER_DEBUG)
#include <iostream>
#endif

uint32 Camera::newCamID = 0;
uint32 Camera::camCount = 0;
/*********************************************/
Camera::Camera(IBaseOutput* out)
{
	Point3D			eye(2.0f, 2.0f, 2.0f);
	Point3D			view(0.0f, 0.0f, 0.0f);

	if ( !out ) {
		throw( std::logic_error("Null valued pointer passed to the Camera Constructor!"));
	}

	// Assign a camera id and increment the static counter
	camID = newCamID;
	newCamID++;
	camCount++;
	output = out;
	Config* cfg = Config::Instance();						// Get the configuration instance

#if defined FSAA
	if ( cfg->test("FSAA") ) {
		sint32 fsaa = cfg->get("FSAA");
		cfg->set("INTERNAL_SCREEN_WIDTH",fsaa*cfg->get("SCREEN_WIDTH"));
		cfg->set("INTERNAL_SCREEN_HEIGHT",fsaa*cfg->get("SCREEN_HEIGHT"));
	} else {
		cfg->set("INTERNAL_SCREEN_WIDTH",cfg->get("SCREEN_WIDTH"));
		cfg->set("INTERNAL_SCREEN_HEIGHT",cfg->get("SCREEN_HEIGHT"));
	}
#else
	cfg->set("INTERNAL_SCREEN_WIDTH",cfg->get("SCREEN_WIDTH"));
	cfg->set("INTERNAL_SCREEN_HEIGHT",cfg->get("SCREEN_HEIGHT"));
#endif

	initScalingTransformation( cfg->get("INTERNAL_SCREEN_WIDTH"), cfg->get("INTERNAL_SCREEN_HEIGHT"));	// Screen resolution setup
	output->setResolution( cfg->get("SCREEN_WIDTH"), cfg->get("SCREEN_HEIGHT"));
	setViewParameters( eye, view );							// Initialize the camera with standard view parameters

	BufferControl::Instance()->newCamera( camID );					// Create buffers for our new camera
}
/*********************************************/
void Camera::drawPoint2D( const Point3D& A )
{
	BufferControl::activeCam = camID;
	A.draw();
}
/*********************************************/
void Camera::drawPoint2D( const Point3D& A, const uint16 r )
{
	BufferControl::activeCam = camID;
	A.draw(r);
}
/*********************************************/
void Camera::drawPoint3D( const Point3D& A )
{
	BufferControl::activeCam = camID;
	Point3D		B;
	B = A;
	
	viewTransform( B );
	projectPoint3D( B );
	winToView( B );

	B.draw();
}
/*********************************************/
void Camera::drawLine2D( const Line3D& l )
{
	BufferControl::activeCam = camID;
#if defined CLIP2D_COHENSUTHERLAND
	(l.clipLineCohenSutherland2D()).draw();
#else
	l.draw();
#endif
}
/*********************************************/
void Camera::drawLine3D( const Line3D& l )
{
	BufferControl::activeCam = camID;
	Point3D		A,B;
	l.getPoints(A,B);

	viewTransform( A );
	viewTransform( B );
	projectPoint3D( A );
	projectPoint3D( B );
	winToView( A );
	winToView( B );

#if defined CLIP2D_COHENSUTHERLAND
	(Line3D(A,B).clipLineCohenSutherland2D()).draw();
#else
	Line3D(A,B).draw();
#endif
}
/*********************************************/
void Camera::drawPolygon3D( const Polygon& p )
{
	BufferControl::activeCam = camID;
	Polygon		local = p;
	Point3D 	punt;
	
	for( sint8 i = 0; i<3; i++ ) {
		punt = local.getPoint( i );
		
		viewTransform( punt );		// transform the vertex to camera coordinate system
		projectPoint3D( punt );		// project the vertex onto the projection plane
		winToView( punt );		// perform a window to viewport transformation to scale the image
		local.setPoint( i, punt );	// set the point in the local buffer
	}

#ifdef ZBUFFERFILL
	local.drawZBuffer();
#elif defined WIREFRAME
	local.drawWireFrame();
#endif
}
/*********************************************/
void Camera::drawPolygon2D( const Polygon& p )
{
	BufferControl::activeCam = camID;
#ifdef ZBUFFERFILL
	p.drawZBuffer();
#elif defined WIREFRAME
	p.drawWireFrame();
#endif
}
/*********************************************/
Color Camera::getFSAAPixelSample( const uint32& x, const uint32& y ) const
{
	Color		color;
	uint32		r = 0;
	uint32		g = 0;
	uint32		b = 0;
	ImageBuffer<Color>* frame = BufferControl::Instance()->getFrameBuffer();
	sint32		fsaa = Config::Instance()->get("FSAA");
	
	for ( uint32 lx = 0; lx<fsaa; lx++ ) {
		for ( uint32 ly = 0; ly<fsaa; ly++ ) {
			uint8	lr, lg, lb;
			frame->getPixel( (x*fsaa)+lx, (y*fsaa)+ly ).getRGBComponents( lr, lg, lb );
			r = r + (uint32)lr;
			g = g + (uint32)lg;
			b = b + (uint32)lb;
		}
	}
	r = r / (fsaa*fsaa);
	g = g / (fsaa*fsaa);
	b = b / (fsaa*fsaa);

	color.setRGBComponents( r, g, b );
	return ( color );
}
/*********************************************/
void Camera::flush( const uint32 buffer ) const
{
	BufferControl::activeCam = camID;
	BufferControl* bControl = BufferControl::Instance();
	uint32 width = Config::Instance()->get("SCREEN_WIDTH");
	uint32 height = Config::Instance()->get("SCREEN_HEIGHT");
	
	// flush the frame buffer to the output object
	if ( buffer == BUFFER_FRAME ) {
		ImageBuffer<Color>* frame = bControl->getFrameBuffer();
		for ( uint32 x = 0; x<width; x++ ) {
			for ( uint32 y = 0; y<height; y++ ) {	
#if defined FSAA
				output->setPixel( x, height - 1 - y, getFSAAPixelSample( x,y ));
#else
				output->setPixel( x, height - 1 - y, frame->getPixel( x,y ));
#endif
			}
		}
		
	// flush the depth buffer to the output object ( floats converted to greyscales )
	} else if ( buffer == BUFFER_DEPTH ) {
		Color			p;
		fl32			largest, smallest, increment, tint;
		ImageBuffer<fl64>*	depth = bControl->getDepthBuffer();

		largest = 0;
		smallest = FLT_MAX;
		for ( uint32 x = 0; x<width; x++ ) {
			for ( uint32 y = 0; y<height; y++ ) {
				fl64 	pixel = depth->getPixel( x,y );
				if (( pixel > largest ) && ( pixel != FLT_MAX )) largest = pixel;
				if ( pixel < smallest )	smallest = pixel;
			}
		}

		increment = (largest - smallest)/100;
		for ( uint32 x = 0; x<width; x++ ) {
			for ( uint32 y = 0; y<height; y++ ) {
				if ( depth->getPixel( x, y ) == FLT_MAX ) {
					p.setRGBComponents(DEPTH_FLUSH_BACKGROUND);
					output->setPixel( x, height -y - 1, p );
				} else {
					tint = 255*(((depth->getPixel( x, y ) - smallest)/increment)/100);
					p.setGreyScale(tint);
					output->setPixel( x, height - y - 1, p );
				}
			}
		}
	} else {
		throw(std::logic_error("Illegal buffer identifier passed to Rasterizer::flush(...)!"));
	}
	
	output->flush();
}
/*********************************************/
uint32 Camera::getID() const
{
	return( camID );
}
/*********************************************/
void Camera::viewTransform( Point3D& p ) const
{
	p = viewingTransformation * p;
#ifdef RENDER_DEBUG
	std::cout << "VT: ( " << p.x << ", " << p.y << ", " << p.z << " )" << std::endl;
#endif
}
/*********************************************/
inline void Camera::winToView( Point3D& p ) const
{
	fl32 	Sx = (wu[0]-wl[0])/(vu[0]-vl[0]);
	fl32	Sy = (wu[1]-wl[1])/(vu[1]-vl[1]);
	
	Sx = 1/Sx;
	Sy = 1/Sy;
	
	p.x = ( p.x - wl[0])*Sx + vl[0];
	p.y = ( p.y - wl[1])*Sy + vl[1];
}
/*********************************************/
inline void Camera::projectPoint3D( Point3D& p ) const 
{
	p.x =  -(p.x/p.z) ;
	p.y =  -(p.y/p.z) ;
	p.z = -p.z;
}
/*********************************************/
void Camera::setViewParameters( const Point3D& eyePoint, const Point3D& viewPoint )
{
	CVector::CVector<fl64>	x,y,z;		// Our 3D Base
	CVector::CVector<fl64>	eye, view;
	CVector::CVector<fl64>	up;
	CMatrix::CMatrix<fl64>	translation;
	
	// Up vector standard values =)
	up[0] = 0.0;
	up[1] = 1.0;
	up[2] = 0.0;
	up[3] = 0.0;
	
	// create eye/view vectors from the 3D points
	eye = eyePoint;
	view = viewPoint;
	
	z = eye - view;
	z.normalise();
	
	x = up * z;
	x.normalise();
	
	y = z * x;
	y.normalise();
	
	// Set up the viewing matrix
	viewingTransformation.initModelViewMatrix( x,y,z );
	viewingTransformation = !viewingTransformation;
	
	
	translation[ 3] = eye[0];
	translation[13] = eye[1];
	translation[23] = eye[2];
#ifdef RENDER_DEBUG
	std::cout << "View matrix prior to translation!" << std::endl;
	std::cout << viewingTransformation << "*" << !translation << std::endl;
#endif
	viewingTransformation = viewingTransformation * (!translation);
#ifdef RENDER_DEBUG
	std::cout << std::endl;
	std::cout << "Final view transformation matrix: " << std::endl;
	std::cout << viewingTransformation << std::endl;
#endif
}
/*********************************************/
void Camera::initScalingTransformation( const uint32& width, const uint32& height )
{
	if (( width < 1 ) || ( height < 1 )) {
		throw( std::logic_error("Negative screen resolution!") );
	}

#ifdef ASPECT_RATIO_PRINT
	// Calculating the aspect ratio (debug version)
	uint32 rest;
	uint32 a = width;
	uint32 b = height;
	while(b != 0){
		rest=a % b;
		a = b;
		b = rest;
	}
	fl64 aspectNominator = width/a;
	fl64 aspectDeNominator = height/a;
	fl64 aspectRatio = aspectNominator/aspectDeNominator;

	std::cout << "Aspect ratio: " << aspectNominator << ":" << aspectDeNominator << std::endl;
	std::cout << "AR: " << aspectRatio << std::endl;
#endif

	// Window to Viewport transformation setup
	wu[0] = width/height;
	wu[1] = 1;
	wl[0] = -wu[0];
	wl[1] = -wu[1];

	vl[0] = 0;
	vl[1] = 0;
	vu[0] = width;
	vu[1] = height;
}
/*********************************************/
void Camera::activateCamera() const
{
	BufferControl::activeCam = camID;
}
/*********************************************/
void Camera::clear( const uint32 buffer )
{
	BufferControl::activeCam = camID;
	BufferControl::Instance()->clear( buffer );
}
/*********************************************/
Camera::~Camera()
{
	camCount--;

	// Get rid of used memory
	BufferControl::Instance()->delCamera( camID );

	// Global Class destruction
	if ( camCount == 0 ) {
		BufferControl::Instance()->Destruct();
		Config::Instance()->Destruct();
	}
}
/*********************************************/